Skip to content

fix(editor): keep nested indentation for set operations in subqueries and CTEs (#1698)#1710

Merged
datlechin merged 1 commit into
mainfrom
fix-1698-sql-formatter-nesting
Jun 17, 2026
Merged

fix(editor): keep nested indentation for set operations in subqueries and CTEs (#1698)#1710
datlechin merged 1 commit into
mainfrom
fix-1698-sql-formatter-nesting

Conversation

@datlechin

Copy link
Copy Markdown
Member

Fixes #1698.

Problem

The SQL formatter mangled a UNION (or INTERSECT/EXCEPT) inside a derived table or CTE: it collapsed the nested indentation, put blank lines around the set-operation keyword, and appended the closing ) directly to the last inner SELECT (...current) AS final).

Root cause

indent was a bare counter, and handleSetOperation assumed set operations are always top-level: it did indent = 0 and clauseStack.removeAll() on every UNION/INTERSECT/EXCEPT. Inside a subquery that destroyed the .subquery frame, so the next SELECT formatted at indent 0 and the closing ) fell through handleCloseParen's block branch into the bare-append fallback.

Fix

Refactor to a nesting-derived model rather than patch the one symptom:

  • indent is now a computed property equal to the number of open block frames (subquery / CREATE TABLE body). Every manual indent += 1 / -= 1 / = 0 is gone, so indentation can't be clobbered by clause handling.
  • handleSetOperation pops only the current SELECT's clause frames back to the enclosing block, keeps the block frame, emits the keyword at the real nesting indent, and uses blank-line separation only at the top level (single newlines when nested).
  • Per-statement state (including selectColumnIndentStack) is cleared on ;, fixing a stack imbalance.
  • Oracle MINUS routes through the same path.

For the reported query the formatter now indents the derived table one level, aligns both UNION blocks, and puts ) AS final on its own line.

Tests

13 new cases in SQLFormatterServiceTests: UNION / UNION ALL / INTERSECT / EXCEPT in a derived table, UNION in a CTE, UNION in a nested subquery, chained top-level unions, top-level INTERSECT, INSERT ... SELECT, window-frame ROWS BETWEEN stays inline, and two idempotency checks. The full suite (49 tests, including every pre-existing case: union, subqueryInFrom, cte, createTable, windowFunction) passes with no regressions.

Notes

  • Output uses TablePro's existing house style (2-space indent + column river), matching the existing subqueryInFrom/cte tests, not the 4-space style in the issue's "expected" block.
  • swiftlint --strict clean on the formatter source.
  • Out of scope (would need the parser/AST rewrite): reflowing scalar subqueries inside the SELECT column river, ON CONFLICT / ON DUPLICATE KEY UPDATE bodies, and MSSQL [bracket] identifiers (conflicts with Postgres array subscripts).

@datlechin datlechin force-pushed the fix-1698-sql-formatter-nesting branch from 7e6ba04 to 0c9ffeb Compare June 17, 2026 16:06
@datlechin datlechin merged commit 8759a94 into main Jun 17, 2026
2 of 3 checks passed
@datlechin datlechin deleted the fix-1698-sql-formatter-nesting branch June 17, 2026 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL Formatter Does Not Properly Indent UNION Queries and Derived Tables

1 participant